﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;

namespace Toybox.NicoSharp.Live.Primitives
{
	using Logger;

	/// <summary>
	/// MessageClient の Abstract クラス
	/// </summary>
	public abstract class AbstractMessageClient : INotifyPropertyChanged
	{

		#region [Static]

		public const string VERSION = "20061206";

		//public const string FORMAT_THREAD = "<thread thread=\"{0}\" res_from=\"{1}\" version=\"20061206\" scores=\"1\"/>\0";

		#endregion [Static]


		#region [INotifyPropertyChanged]

		/// <summary>
		/// Property 変更イベント
		/// </summary>
		public event PropertyChangedEventHandler PropertyChanged;

		/// <summary>
		/// Property 変更イベント
		/// </summary>
		/// <param name="property"></param>
		protected void OnPropertyChanged(string property)
		{
			if (PropertyChanged != null)
				PropertyChanged(this, new PropertyChangedEventArgs(property));
		}

		#endregion [INotifyPropertyChanged]


		#region Events

		/// <summary>
		/// 接続開始イベント
		/// </summary>
		public event EventHandler Connecting;

		/// <summary>
		/// 接続完了イベント
		/// </summary>
		public event EventHandler Connected;

		/// <summary>
		/// 切断イベント
		/// </summary>
		public event EventHandler Disconnected;

		/// <summary>
		/// 接続開始
		/// </summary>
		/// <param name="e"></param>
		protected virtual void OnConnecting(EventArgs e)
		{
			if (Connecting != null)
				Connecting(this, e);
		}

		/// <summary>
		/// 接続完了
		/// </summary>
		/// <param name="e"></param>
		protected virtual void OnConnected(EventArgs e)
		{
			if (Connected != null)
				Connected(this, e);
		}

		/// <summary>
		/// 切断
		/// </summary>
		/// <param name="e"></param>
		protected virtual void OnDisconnected(EventArgs e)
		{
			if (Disconnected != null)
				Disconnected(this, e);
		}

		#endregion Events


		#region Constructor

		/// <summary>
		/// Constructor
		/// </summary>
		public AbstractMessageClient()
		{
			// 初期値
			this.State = ConnectionState.Disconnected;
			this.Timeout = 5 * 60 * 1000;
			this.BufferSize = 4096;
		}

		#endregion Constructor


		#region Public Members

		/// <summary>
		/// 接続状態
		/// </summary>
		[DefaultValue(ConnectionState.Disconnected)]
		public ConnectionState State
		{
			get { return this._state; }
			protected set
			{
				if (this._state == value) return;

				this._state = value;

				this.OnPropertyChanged("State");
			}
		}
		private ConnectionState _state;

		/// <summary>
		/// Timeout 時間
		/// </summary>
		public int Timeout
		{
			get { return this._timeout; }
			set
			{
				if (value < 0)
					throw new ArgumentOutOfRangeException("Timeoutは0未満にはできません。");

				if (this.State != ConnectionState.Disconnected)
					throw new ArgumentException("接続中にTimeoutを変更できません");

				this._timeout = value;
			}
		}
		private int _timeout;

		/// <summary>
		/// Buffer Size
		/// </summary>
		public int BufferSize
		{
			get { return this._bufferSize; }
			set
			{
				if (value <= 0)
					throw new ArgumentOutOfRangeException("BufferSizeは0以下にはできません。");

				if (this.State != ConnectionState.Disconnected)
					throw new ArgumentException("接続中にBufferSizeを変更できません");

				this._bufferSize = value;
			}
		}
		private int _bufferSize;

		#endregion Public Members

		#region Protected Members

		/// <summary>
		/// TcpClient
		/// </summary>
		protected TcpClient InnerTcpClient;

		/// <summary>
		/// Thread
		/// </summary>
		protected Thread InnerThread;

		/// <summary>
		/// Task
		/// </summary>
		protected Task InnerTask;

		#endregion Protected Members


		#region Public Methods

		/// <summary>
		/// 切断 (Thread)
		/// </summary>
		public virtual void ThreadStop()
		{
			this.Stop();

			if (this.InnerThread != null && this.InnerThread.IsAlive)
				this.InnerThread.Join();
		}

		/// <summary>
		/// 切断 (Task)
		/// </summary>
		public virtual void TaskStop()
		{
			this.Stop();

			if (this.InnerTask != null)
				this.InnerTask.Wait();
		}

		/// <summary>
		/// 切断
		/// </summary>
		public virtual void Stop()
		{
			if (this.InnerTcpClient != null)
				this.InnerTcpClient.Close();
		}

		#endregion Public Methods

		#region Protected Methods

		/// <summary>
		/// 接続開始
		/// </summary>
		/// <param name="address"></param>
		/// <param name="port"></param>
		/// <param name="thread"></param>
		/// <param name="count"></param>
		protected virtual void Connect(string address, int port, string thread, int count)
		{
			// TCP接続
			if (this.InnerTcpClient != null)
				this.InnerTcpClient.Close();

			try
			{
				using (this.InnerTcpClient = new TcpClient(address, port))
				using (NetworkStream ns = this.InnerTcpClient.GetStream())
				{
					this.InnerTcpClient.ReceiveTimeout = this.Timeout;
					this.State = ConnectionState.Connected;

					this.OnConnected(EventArgs.Empty);

					#region 初期メッセージ送信

					//string msg = String.Format(FORMAT_THREAD, thread, -count);
					string msg = this.CreateStartMessage(thread, count);
					byte[] sendBytes = Encoding.UTF8.GetBytes(msg);
					ns.Write(sendBytes, 0, sendBytes.Length);

					#endregion

					#region 受信

					string temp = string.Empty;
					byte[] resBytes = new byte[this.BufferSize];
					int resSize = 0;

					// 受信
					for (; ns.CanRead; )
					{
						resBytes.Initialize();

						resSize = ns.Read(resBytes, 0, resBytes.Length);
						if (resSize == 0) break;

						string message = Encoding.UTF8.GetString(resBytes, 0, resSize);
						List<string> elements = new List<string>(message.Split(new char[] { '\0' }, StringSplitOptions.RemoveEmptyEntries));

						elements[0] = temp + elements[0];
						int last = elements.Count - 1;
						if (elements[last].LastIndexOf(">") == elements[last].Length - 1)
						{
							temp = string.Empty;
						}
						else
						{
							temp = elements[last];
							elements.RemoveAt(last);
						}

						// メッセージ解析
						foreach (string element in elements)
							this.DeserializeMessage(element);
					}

					#endregion

				}
			}
			catch (Exception ex)
			{
				TraceLogger.WriteLine(ex.ToString());
			}
			finally
			{
				this.State = ConnectionState.Disconnected;
				this.OnDisconnected(EventArgs.Empty);
			}
		}

		/// <summary>
		/// 初期メッセージ作成
		/// </summary>
		/// <returns></returns>
		protected virtual string CreateStartMessage(string thread, int count = 0, bool isScores = true)
		{
			// "<thread thread=\"{0}\" res_from=\"{1}\" version=\"20061206\" scores=\"1\"/>\0";
			XmlDocument doc = new XmlDocument();
			XmlElement element = doc.CreateElement("thread");
			element.SetAttribute("thread", thread);
			element.SetAttribute("res_from", (-count).ToString());
			element.SetAttribute("version", VERSION);
			if (isScores)
				element.SetAttribute("scores", "1");
			doc.AppendChild(element);

			try
			{
				using (var sw = new StringWriter())
				using (var tw = new XmlTextWriter(sw))
				{
					doc.WriteTo(tw);
					return sw.ToString() + "\0";
				}
			}
			catch (Exception ex)
			{
				TraceLogger.WriteLine(ex.ToString());
				return null;
			}
		}

		/// <summary>
		/// Message の Deserialize
		/// </summary>
		/// <param name="message"></param>
		protected abstract void DeserializeMessage(string message);

		#endregion Protected Methods

	}
}
